﻿using System;
using System.Drawing;
using System.Drawing.Imaging;

namespace BitmapPtr
{
    public class EditableBitmap : IDisposable
    {
        Bitmap bitmap;
        int stride;
        int pixelFormatSize;

        SharedPinnedByteArray byteArray;

        /// <summary>
        /// Gets the pixel format size in bytes (not bits, as with Image.GetPixelFormatSize()).
        /// </summary>
        public int PixelFormatSize
        {
            get { return pixelFormatSize; }
        }

        /// <summary>
        /// Gets the stride of the bitmap.
        /// </summary>
        public int Stride
        {
            get { return stride; }
        }

        /// <summary>
        /// Gets the underlying
        /// that this EditableBitmap wraps.
        /// </summary>
        public Bitmap Bitmap
        {
            get { return bitmap; }
            set { bitmap = value; }
        }

        /// <summary>
        /// Gets an array that contains the bitmap bit buffer.
        /// </summary>
        public byte[] Bits
        {
            get { return byteArray.bits; }
        }

        private EditableBitmap owner;

        /// <summary>
        /// This property's value will be null if this EditableBitmap is not a view on another 
        /// </summary>
        public EditableBitmap Owner
        {
            get { return owner; }
        }


        /// <summary>
        /// Gets a safe pointer to the buffer containing the bitmap bits.
        /// </summary>
        public IntPtr BitPtr
        {
            get
            {
                return byteArray.bitPtr;
            }
        }

        /// <summary>
        /// Creates a new EditableBitmap with the specified pixel format, 
        /// and copies the bitmap passed in onto the buffer.
        /// </summary>
        public EditableBitmap(Bitmap source, PixelFormat format) : this(source.Width, source.Height, format)
        {
            Graphics g = Graphics.FromImage(bitmap);
            g.DrawImageUnscaledAndClipped(source, new Rectangle(0, 0, source.Width, source.Height));
            g.Dispose();
        }

        /// <summary>
        /// Creates a new EditableBitmap with the specified pixel format and size, 
        /// and copies the bitmap passed in onto the buffer. The source bitmap is stretched to 
        /// fit the new size.
        /// </summary>
        public EditableBitmap(Bitmap source, int Width, int Height, PixelFormat format) : this(Width, Height, format)
        {
            Graphics g = Graphics.FromImage(bitmap);
            g.DrawImage(source, 0, 0, Width, Height);
            g.Dispose();
        }

        /// <summary>
        /// Creates a new EditableBitmap containing a copy of the specified source bitmap.
        /// </summary>
        public EditableBitmap(Bitmap source) : this(source, source.PixelFormat)
        {

        }

        /// <summary>
        /// Creates a new, blank EditableBitmap with the specified width, height, and pixel format.
        /// </summary>
        public EditableBitmap(int width, int height, PixelFormat format)
        {
            pixelFormatSize = Image.GetPixelFormatSize(format) / 8;
            stride = width * pixelFormatSize;
            int padding = (stride % 4);
            stride += padding == 0 ? 0 : 4 - padding;                 //pad out to multiple of 4
            byteArray = new SharedPinnedByteArray(stride * height);
            bitmap = new Bitmap(width, height, stride, format, byteArray.bitPtr);
        }

        #region IDisposable 
        private bool disposed;

        public bool Disposed
        {
            get { return disposed; }
        }
        public void Dispose()
        {
            Dispose(true);
        }
        protected void Dispose(bool disposing)
        {
            if (disposed)
                return;

            bitmap.Dispose();
            byteArray.ReleaseReference();
            disposed = true;

            //Set managed object refs to null if explicitly disposing, so that they can be cleaned up by the GC.
            if (disposing)
            {
                owner = null;
                bitmap = null;
            }
        }

        ~EditableBitmap()
        {
            Dispose(false);
        }
        #endregion
    }
}
